package som;

import java.util.Arrays;

import utils.RandomGenerator;

/**
 * Class represents the data to be classified
 */
public class Sample {

	private double[] values; // data vector
	private int length; // data vector's length
	private int classification; // sample's classification
	private int index; // sample's index in input file
	
	/**
	 * Constructor- creates a new sample
	 * @param values sample's data
	 * @param classification sample's classification
	 * @param index sample's index in input file
	 */
	public Sample(double[] values, int classification, int index) {
		this.values = values;
		length = values.length;
		this.classification = classification;
		this.index = index;
	}	

	/**
	 * Generates a sample initialized with random values (0 or 1)
	 * for the data vector
	 * @param length length of the data vector
	 * @return a sample initialized with random values
	 */
	public static Sample generateSample(int length) {
		
		// allocate data vector
		double[] values = new double[length];
		
		// choose a random value for each data entry
		for (int i = 0 ; i < length ; ++i) {
			values[i] = RandomGenerator.nextInt(2);
		}
		
		// return a sample with generated data
		return new Sample(values, -1, -1); // generated sample is unlabeled, set arbitrary classification and index
	}
	
	/**
	 * Returns sample's data vector
	 * @return sample's data vector
	 */
	public double[] getValues() {
		return values;
	}
	
	/**
	 * Returns i'th entry of sample's data vector
	 * @param i index of wanted entry
	 * @return i'th entry of sample's data vector
	 */
	public double getValue(int i) {
		return values[i];
	}
	
	/**
	 * Returns sample's classification
	 * @return sample's classification
	 */
	public int getClassification() {
		return classification;
	}
	
	/**
	 * Return's samples index in input file 
	 * @return sample's index in input file
	 */
	public int getIndex() {
		return index;
	}
	
	/**
	 * Returns euclidean distance between this sample and given sample
	 * @param otherSample sample to calculate the distance from
	 * @return euclidean distance between this sample and given sample
	 */
	public double getDistance(Sample otherSample)
	{
		double sum = 0;
		for(int i = 0 ; i < length ; ++i)
		{
			sum += (values[i]-otherSample.getValue(i))*(values[i]-otherSample.getValue(i));
		}
		return Math.sqrt(sum);
	}
	
	/**
	 * Updates sample's data values according to the SOM update rule
	 * @param otherSample the sample that the update is based on the distance from it
	 * @param learningRate learning rate for the update rule
	 * @param neighborhoodRate rate according to containing cell's proximity to the cell being updated (could
	 * be the containing cell or a neighboring cell)
	 */
	public void updateValues(Sample otherSample, double learningRate, double neighborhoodRate)
	{
		// update each value in the data vector
		for(int i = 0 ; i < length ; ++i)
		{
			values[i] = values[i] + learningRate * neighborhoodRate * (otherSample.getValue(i)-values[i]);
		}
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + length;
		result = prime * result + Arrays.hashCode(values);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Sample other = (Sample) obj;
		if (length != other.length)
			return false;
		if (!Arrays.equals(values, other.values))
			return false;
		return true;
	}
}
